/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.interpreter.backends.rete;

import java.util.ArrayList;
import java.util.Collection;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

/**
 *
 * @author Laurent WOUTERS
 */
public class BetaMemory implements TokenHolder {
    private static BetaMemory dummy;
    public static synchronized BetaMemory getDummy() {
        if (dummy != null)
            return dummy;
        dummy = new BetaMemory();
        dummy.tokens.add(new Token(null));
        return dummy;
    }
    
    private Map<Token, Map<TripleFact, Token>> mapAscendants;
    private List<Token> tokens;
    private LinkedList<TokenActivable> children;
    private List<Binder> binders;
    
    @Override public Collection<Token> getTokens() { return tokens; }
    @Override public Collection<TokenActivable> getChildren() { return children; }
    @Override public void addChild(TokenActivable activable) { children.addFirst(activable); }
    @Override public void removeChild(TokenActivable activable) { children.remove(activable); }
    @Override public void removeAllChildren() { children.clear(); }
    public void addBinders(Collection<Binder> binders) {
        this.binders.addAll(binders);
        updateTokens(binders);
    }
    public void removeBinders(Collection<Binder> binders) { this.binders.removeAll(binders); }
    
    public BetaMemory() {
        mapAscendants = new IdentityHashMap<Token, Map<TripleFact, Token>>();
        children = new LinkedList<TokenActivable>();
        tokens = new ArrayList<Token>();
        binders = new ArrayList<Binder>();
    }
    
    public void activate(Token token, TripleFact fact) {
        Token newToken = buildToken(token, fact);
        for (TokenActivable child : children)
            child.activateToken(newToken);
    }
    public void activate(Iterator<Couple> buffer) {
        Collection<Token> result = new ArrayList<Token>();
        while (buffer.hasNext()) {
            Couple couple = buffer.next();
            result.add(buildToken(couple.token, couple.fact));
        }
        if (!result.isEmpty())
            for (TokenActivable child : children)
                child.activateTokens(new FastBuffer<Token>(result));
    }
    
    private Token buildToken(Token token, TripleFact fact) {
        Token newToken = new Token(token);
        for (Binder binder : binders)
            binder.execute(newToken, fact);
        Map<TripleFact, Token> subMap = mapAscendants.get(token);
        if (subMap == null) {
            subMap = new IdentityHashMap<TripleFact, Token>();
            mapAscendants.put(token, subMap);
        }
        subMap.put(fact, newToken);
        tokens.add(newToken);
        return newToken;
    }
    
    public void deactivate(Token token, TripleFact fact) {
        Token newToken = getNewToken(token, fact);
        for (TokenActivable child : children)
            child.deactivateToken(newToken);
    }
    public void deactivate(Iterator<Couple> buffer) {
        Collection<Token> result = new ArrayList<Token>();
        while (buffer.hasNext()) {
            Couple couple = buffer.next();
            result.add(getNewToken(couple.token, couple.fact));
        }
        if (!result.isEmpty())
            for (TokenActivable child : children)
                child.deactivateTokens(new FastBuffer<Token>(result));
    }
    
    private Token getNewToken(Token token, TripleFact fact) {
        Map<TripleFact, Token> subMap = mapAscendants.get(token);
        Token newToken = subMap.get(fact);
        subMap.remove(fact);
        if (subMap.isEmpty())
            mapAscendants.remove(token);
        tokens.remove(newToken);
        return newToken;
    }
    
    public void push() {
        int size = tokens.size();
        if (size != 0) {
            if (size == 1) {
                Token token = tokens.get(0);
                for (TokenActivable child : children)
                    child.activateToken(token);
            } else {
                for (TokenActivable child : children)
                    child.activateTokens(new FastBuffer<Token>(tokens));
            }
        }
    }
    
    private void updateTokens(Collection<Binder> binders) {
        for (Token base : mapAscendants.keySet()) {
            Map<TripleFact, Token> subMap = mapAscendants.get(base);
            for (Entry<TripleFact, Token> entry : subMap.entrySet())
                for (Binder binder : binders)
                    binder.execute(entry.getValue(), entry.getKey());
        }
    }
    
    public BetaJoinNode resolveJoin(FactHolder alpha, Collection<BetaJoinNodeTest> tests) {
        for (TokenActivable child : children) {
            if (child instanceof BetaJoinNode) {
                BetaJoinNode join = (BetaJoinNode)child;
                if (join.match(alpha, tests))
                    return join;
            }
        }
        BetaJoinNode join = new BetaJoinNode(alpha, this, tests);
        return join;
    }
}
